<template>
  <Teleport :to="props.appendDom" :disabled="!props.stopAppend">
    <div class="vc-modal-root" v-if="modalVisible">
      <div
        class="vc-mask"
        @click="props.maskClosable && close()"
        v-if="props.mask"
        :style="{ ...props.maskStyle, zIndex: Number(props.zIndex) }"
      >
      </div>
      <div class="vc-modal-wrap" :style="{ zIndex: Number(props.zIndex) }">
        <div
          class="vc-modal"
          :style="{ ...getModalStyle, ...dragStyle }"
          :draggable="props.draggable"
          @dragstart="dragstart"
          @dragend="dragend"
        >
          <template v-if="slotCustom">
            <slot name="customContent"></slot>
          </template>
          <template v-else>
            <div class="vc-modal-content">
              <button
                v-if="props.closable"
                @click="close"
                class="vc-modal-close"
                :style="{ zIndex: Number(props.zIndex) + 10 }"
              >
                <slot v-if="slotCloseIcon" name="closeIcon"></slot>
                <createIcon v-else-if="props.closeIcon" :icon="props.closeIcon" />
                <CloseOutlined v-else />
              </button>
              <div class="vc-modal-header">
                <div class="vc-modal-header_title">{{ props.title }}</div>
              </div>
              <div class="vc-modal-body" :style="props.bodyStyle">
                <slot v-if="slotDefault"></slot>
                <template v-else>
                  {{ props.content }}
                </template>
              </div>
              <div class="vc-modal-footer" v-if="slotFooter">
                <slot name="footer"></slot>
              </div>
            </div>
          </template>
        </div>
      </div>
    </div>
  </Teleport>
</template>

<script>
  export default {
    name: 'VcModal',
  };
</script>

<script setup>
  import { isString } from '@vueuse/core';
  import { onMounted, getCurrentInstance, computed, onUnmounted, useSlots } from 'vue';
  import { CloseOutlined } from '@ant-design/icons-vue';
  import createIcon from '@/utils/createIcon.js';
  const slotDefault = !!useSlots().default;
  const slotCustom = !!useSlots().customContent;
  const slotCloseIcon = !!useSlots().closeIcon;
  const slotFooter = !!useSlots().footer;

  const props = defineProps({
    modelValue: {
      type: Boolean,
      default: undefined,
    },
    mask: {
      type: Boolean,
      default: true,
    },
    // 点击遮罩是否允许关闭
    maskClosable: {
      type: Boolean,
      default: true,
    },
    stopAppend: {
      type: Boolean,
      default: true,
    },
    appendDom: {
      type: String,
      default: 'body',
    },
    width: {
      type: [Number, String],
      default: 520,
    },
    modalStyle: {
      type: Object,
      default: () => {},
    },
    bodyStyle: {
      type: Object,
      default: () => {},
    },
    maskStyle: {
      type: Object,
      default: () => {},
    },
    title: {
      type: String,
      default: '',
    },
    closable: {
      type: Boolean,
      default: true,
    },
    centered: {
      type: Boolean,
      default: false,
    },
    keyboard: {
      type: Boolean,
      default: true,
    },
    content: {
      type: String,
      default: '',
    },
    zIndex: {
      type: [Number, String],
      default: 1000,
    },
    beforeClose: {
      type: Function,
      default: null,
    },
    draggable: {
      type: Boolean,
      default: false,
    },
  });
  const emits = defineEmits(['update:modelValue', 'open', 'close']);
  const currentVisible = ref(false);
  const modalVisible = computed({
    get() {
      const visible = props.modelValue || currentVisible.value;
      if (visible) {
        emits('open');
      } else if (!visible) {
        emits('close');
      }
      return visible;
    },
    set(value) {
      if (props.modelValue) {
        emits('update:modelValue', value);
      } else {
        currentVisible.value = value;
      }
    },
  });

  const getModalStyle = computed(() => {
    let center = {
      top: '100px',
    };
    if (props.centered) {
      center = {
        top: '50%',
        transform: 'translateY(-50%)',
      };
    }
    return {
      width:
        isString(props.width) && props.width.indexOf('%') > -1
          ? props.width
          : Number(props.width) + 'px',
      ...center,
      ...props.modalStyle,
    };
  });

  // 打开
  const open = () => {
    modalVisible.value = true;
    emits('open');
  };

  // 关闭
  const close = async () => {
    (await props.beforeClose) && props.beforeClose();
    modalVisible.value = false;
    emits('close');
  };

  const closeByESC = (event) => {
    let e = event || window.event || arguments.callee.caller.arguments[0];
    if (e && e.keyCode === 27) {
      close();
    }
  };

  props.keyboard && document.addEventListener('keydown', closeByESC, false);

  onUnmounted(() => {
    props.keyboard && document.removeEventListener('keydown', closeByESC, false);
  });

  // 拖拽
  const diffX = ref(0);
  const diffY = ref(0);
  const dragStyle = ref({});

  /**
   * @description: 拖拽前的回调
   * @param {Object} DragEvent
   * @return void
   */
  const dragstart = (e) => {
    if (!props.draggable) {
      return false;
    }
    document.ondragover = (e) => {
      e.preventDefault();
      e.dataTransfer.dropEffect = 'move';
    };
    //获取鼠标相对于拖拽元素的位置
    diffX.value = e.layerX;
    diffY.value = e.layerY;
  };
  /**
   * @description: 拖拽后的回调
   * @param {Object} DragEvent
   * @return void
   */
  const dragend = (e) => {
    if (!props.draggable) {
      return false;
    }
    document.ondragover = function (e) {
      e.dataTransfer.dropEffect = 'none';
    };
    //获取元素落点位置 鼠标位置-相对于拖拽元素的位置
    dragStyle.value = {
      transform: 'none',
      margin: '0',
      top: e.pageY - diffY.value + 'px',
      left: e.pageX - diffX.value + 'px',
    };
  };

  defineExpose({
    open,
    close,
  });
</script>

<style lang="less" scoped>
  .vc-modal-root {
    box-sizing: border-box;
  }
  .vc-mask {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    height: 100%;
    background-color: @mask;
  }
  .vc-modal-wrap {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    overflow: auto;
    outline: 0;
    pointer-events: none;
  }
  .vc-modal {
    position: relative;
    box-sizing: border-box;
    margin: 0 auto;
    padding: 0;
    color: @modal-color;
    font-size: 14px;
    list-style: none;
    width: auto;
    max-width: calc(100vw - 32px);
    pointer-events: none;
    &-content {
      position: relative;
      background-color: #ffffff;
      background-clip: padding-box;
      border: 0;
      border-radius: 8px;
      box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12),
        0 9px 28px 8px rgba(0, 0, 0, 0.05);
      pointer-events: auto;
      padding: 20px 24px;
    }
    &-close {
      position: absolute;
      top: 17px;
      right: 17px;
      padding: 0;
      color: @mask;
      font-weight: 600;
      line-height: 1;
      text-decoration: none;
      background: transparent;
      border-radius: 4px;
      width: 22px;
      height: 22px;
      border: 0;
      outline: 0;
      cursor: pointer;
      transition: color 0.2s, background-color 0.2s;
    }
    &-header {
      color: @modal-color;
      background: #ffffff;
      border-radius: 8px 8px 0 0;
      margin-bottom: 8px;
      &_title {
        margin: 0;
        color: @modal-color;
        font-weight: 600;
        font-size: 16px;
        line-height: 1.5;
        word-wrap: break-word;
      }
    }
    &-body {
      font-size: 14px;
      line-height: 1.5714285714285714;
      word-wrap: break-word;
    }
    &-footer {
      text-align: end;
      background: transparent;
      margin-top: 12px;
    }
  }
</style>
